-
Using this HTML as an example :
<div id="widget1">Widget 1</div>
<div id="widget2">Widget 2</div>
<div id="widget3">Widget 3</div>
<div id="status"></div>
Java Script
const widgets = [
{ id: "widget1", url: "/data1" },
{ id: "widget2", url: "/data2" },
{ id: "widget3", url: "/data3" },
];
const status = document.getElementById("status");
let completed = 0;
widgets.forEach(w => {
const el = document.getElementById(w.id);
el.innerHTML = "Loading...";
fetch(w.url)
.then(res => {
if (!res.ok) throw new Error("Network error");
return res.text();
})
.then(html => {
el.innerHTML = html;
})
.catch(err => {
el.innerHTML = "Error loading widget";
console.error(err);
})
.finally(() => {
completed++;
if (completed === widgets.length) {
status.innerHTML = "Dashboard loaded";
}
});
});
-
fetch()-
Is asynchronous , but the JS runtime is single-threaded .
-
The browser’s networking stack handles the request in the background using OS threads or the network stack.
-
JS does not block — it registers a callback (the
.then) to be executed later , when the network response arrives. -
You don’t control threads directly; the browser runtime schedules your callbacks on the event loop.
-
-
.then()-
Attaches a callback function (a lambda) to the Promise returned by
fetch(). -
When the Promise resolves (the HTTP request finishes), the JS engine calls this lambda with the response (
res).
-
-
Chaining
.then()-
return res.text()returns another Promise (it's asynchronous). The next.then()in the chain automatically waits for that Promise to resolve. -
In other words: each
.then()schedules the next step. -
The next lambda runs after the previous Promise (
res.text()) resolves. -
Whatever this lambda returns (or doesn’t) is automatically wrapped in a new Promise.
-
JS chains Promises internally, so the next
.catch()or.finally()knows when this step completes. -
The lambda inside the first
.thencan be synchronous or asynchronous . -
If you do just
console.log(res), it’s synchronous. -
If you return a Promise (
res.text()), the next.thenwaits for that Promise to resolve. -
Rule of thumb:
-
If you return a Promise inside a
.then, the next.thenis delayed until that Promise resolves . -
If you return a plain value, it passes immediately to the next
.then.
-
-
Chaining is only needed when the previous step produces a Promise (i.e., an async result).
-
-
.catch()-
If any previous Promise in the chain rejects (network failure, or you
throwinside a.then()), this lambda is executed. -
Equivalent in C to checking
if (!ok)and jumping to the error handler for that thread. -
JS handles the propagation of errors through the chain automatically.
-
-
.finally()-
Always runs after the chain settles, success or failure .
-
Equivalent to your “finally” block in C after the thread finishes, updating counters and printing “Dashboard loaded.”
-
Soft-Equivalence in C, using many threads
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
// Simulated HTTP request
int fetch(const char* url, char** response) {
// pretend network request
if (strcmp(url, "/fail") == 0) return 0; // simulate error
*response = strdup("HTML content");
return 1; // success
}
// Shared state
int completed = 0;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
const int total_widgets = 3;
// Thread function to simulate async fetch
void* fetch_widget(void* arg) {
const char* url = (const char*)arg;
char* html = NULL;
// Step 1 & 2: fetch and check response
int ok = fetch(url, &html);
if (ok) {
// Step 3: update DOM (simulated)
printf("%s: %s\n", url, html);
} else {
// Step 4: catch
printf("%s: Error loading widget\n", url);
}
// Step 5: finally
pthread_mutex_lock(&lock);
completed++;
if (completed == total_widgets) {
printf("Dashboard loaded\n");
}
pthread_mutex_unlock(&lock);
free(html);
return NULL;
}
int main() {
const char* widgets[3] = {"/data1", "/data2", "/data3"};
pthread_t threads[3];
// Launch all requests in parallel
for (int i = 0; i < 3; i++) {
pthread_create(&threads[i], NULL, fetch_widget, (void*)widgets[i]);
}
// Wait for all threads to finish
for (int i = 0; i < 3; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}
-
C threads were just a visual analogy for concurrency, not a literal one-to-one comparison.
-
In C, network requests are blocking by default. This C example used threads to simulate concurrent network requests.
-
A more faithful comparison would be C using non-blocking async I/O (e.g.,
libuv,epoll) instead of threads, but that’s more complex and less intuitive.
Equivalence in C, using concurrency with
libuv
#include <stdio.h>
#include <stdlib.h>
#include <uv.h>
int completed = 0;
const int total_widgets = 3;
// Simulated async HTTP request
void async_fetch(uv_loop_t* loop, const char* url, void (*callback)(const char*, int)) {
// Use a timer to simulate network latency
uv_timer_t* timer = malloc(sizeof(uv_timer_t));
uv_timer_init(loop, timer);
timer->data = (void*)url;
uv_timer_start(timer,
(uv_timer_cb) (void (*)(uv_timer_t*)) [](uv_timer_t* t) {
const char* url = (const char*)t->data;
int success = 1; // simulate success/failure
if (strcmp(url, "/fail") == 0) success = 0;
callback(url, success);
uv_timer_stop(t);
free(t);
},
rand() % 1000, // simulate network delay
0
);
}
// Callback after each request
void on_fetch_complete(const char* url, int success) {
if (success) {
printf("%s: HTML content\n", url);
} else {
printf("%s: Error loading widget\n", url);
}
completed++;
if (completed == total_widgets) {
printf("Dashboard loaded\n");
}
}
int main() {
uv_loop_t* loop = uv_default_loop();
const char* widgets[3] = {"/data1", "/data2", "/data3"};
for (int i = 0; i < 3; i++) {
async_fetch(loop, widgets[i], on_fetch_complete);
}
uv_run(loop, UV_RUN_DEFAULT);
return 0;
}
-
async_fetch()-
Simulates an HTTP request using a non-blocking timer.
-
-
on_fetch_complete()-
Acts as the callback, similar to a
.then()in JS.
-
-
Requests are all started immediately and run concurrently in the event loop, without spawning separate OS threads.
-
When each request “finishes,” its callback executes on the main thread, just like JS Promises execute on the event loop.
-
We track
completedto know when all requests are done, analogous to.finally()in JS.